/*! Buttons for DataTables 1.6.5
 * ©2016-2020 SpryMedia Ltd - datatables.net/license
 */

(function (factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD
    define(['jquery', 'datatables.net'], function ($) {
      return factory($, window, document);
    });
  } else if (typeof exports === 'object') {
    // CommonJS
    module.exports = function (root, $) {
      if (!root) {
        root = window;
      }

      if (!$ || !$.fn.dataTable) {
        $ = require('datatables.net')(root, $).$;
      }

      return factory($, root, root.document);
    };
  } else {
    // Browser
    factory(jQuery, window, document);
  }
}(function ($, window, document, undefined) {
  'use strict';
  var DataTable = $.fn.dataTable;


// Used for namespacing events added to the document by each instance, so they
// can be removed on destroy
  var _instCounter = 0;

// Button namespacing counter for namespacing events on individual buttons
  var _buttonCounter = 0;

  var _dtButtons = DataTable.ext.buttons;

// Allow for jQuery slim
  function _fadeIn(el, duration, fn) {
    if ($.fn.animate) {
      el
        .stop()
        .fadeIn(duration, fn);
    } else {
      el.css('display', 'block');

      if (fn) {
        fn.call(el);
      }
    }
  }

  function _fadeOut(el, duration, fn) {
    if ($.fn.animate) {
      el
        .stop()
        .fadeOut(duration, fn);
    } else {
      el.css('display', 'none');

      if (fn) {
        fn.call(el);
      }
    }
  }

  /**
   * [Buttons description]
   * @param {[type]}
   * @param {[type]}
   */
  var Buttons = function (dt, config) {
    // If not created with a `new` keyword then we return a wrapper function that
    // will take the settings object for a DT. This allows easy use of new instances
    // with the `layout` option - e.g. `topLeft: $.fn.dataTable.Buttons( ... )`.
    if (!(this instanceof Buttons)) {
      return function (settings) {
        return new Buttons(settings, dt).container();
      };
    }

    // If there is no config set it to an empty object
    if (typeof (config) === 'undefined') {
      config = {};
    }

    // Allow a boolean true for defaults
    if (config === true) {
      config = {};
    }

    // For easy configuration of buttons an array can be given
    if (Array.isArray(config)) {
      config = {buttons: config};
    }

    this.c = $.extend(true, {}, Buttons.defaults, config);

    // Don't want a deep copy for the buttons
    if (config.buttons) {
      this.c.buttons = config.buttons;
    }

    this.s = {
      dt: new DataTable.Api(dt),
      buttons: [],
      listenKeys: '',
      namespace: 'dtb' + (_instCounter++)
    };

    this.dom = {
      container: $('<' + this.c.dom.container.tag + '/>')
        .addClass(this.c.dom.container.className)
    };

    this._constructor();
  };


  $.extend(Buttons.prototype, {
    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Public methods
	 */

    /**
     * Get the action of a button
     * @param  {int|string} Button index
     * @return {function}
     *//**
     * Set the action of a button
     * @param  {node} node Button element
     * @param  {function} action Function to set
     * @return {Buttons} Self for chaining
     */
    action: function (node, action) {
      var button = this._nodeToButton(node);

      if (action === undefined) {
        return button.conf.action;
      }

      button.conf.action = action;

      return this;
    },

    /**
     * Add an active class to the button to make to look active or get current
     * active state.
     * @param  {node} node Button element
     * @param  {boolean} [flag] Enable / disable flag
     * @return {Buttons} Self for chaining or boolean for getter
     */
    active: function (node, flag) {
      var button = this._nodeToButton(node);
      var klass = this.c.dom.button.active;
      var jqNode = $(button.node);

      if (flag === undefined) {
        return jqNode.hasClass(klass);
      }

      jqNode.toggleClass(klass, flag === undefined ? true : flag);

      return this;
    },

    /**
     * Add a new button
     * @param {object} config Button configuration object, base string name or function
     * @param {int|string} [idx] Button index for where to insert the button
     * @return {Buttons} Self for chaining
     */
    add: function (config, idx) {
      var buttons = this.s.buttons;

      if (typeof idx === 'string') {
        var split = idx.split('-');
        var base = this.s;

        for (var i = 0, ien = split.length - 1; i < ien; i++) {
          base = base.buttons[split[i] * 1];
        }

        buttons = base.buttons;
        idx = split[split.length - 1] * 1;
      }

      this._expandButton(buttons, config, base !== undefined, idx);
      this._draw();

      return this;
    },

    /**
     * Get the container node for the buttons
     * @return {jQuery} Buttons node
     */
    container: function () {
      return this.dom.container;
    },

    /**
     * Disable a button
     * @param  {node} node Button node
     * @return {Buttons} Self for chaining
     */
    disable: function (node) {
      var button = this._nodeToButton(node);

      $(button.node)
        .addClass(this.c.dom.button.disabled)
        .attr('disabled', true);

      return this;
    },

    /**
     * Destroy the instance, cleaning up event handlers and removing DOM
     * elements
     * @return {Buttons} Self for chaining
     */
    destroy: function () {
      // Key event listener
      $('body').off('keyup.' + this.s.namespace);

      // Individual button destroy (so they can remove their own events if
      // needed). Take a copy as the array is modified by `remove`
      var buttons = this.s.buttons.slice();
      var i, ien;

      for (i = 0, ien = buttons.length; i < ien; i++) {
        this.remove(buttons[i].node);
      }

      // Container
      this.dom.container.remove();

      // Remove from the settings object collection
      var buttonInsts = this.s.dt.settings()[0];

      for (i = 0, ien = buttonInsts.length; i < ien; i++) {
        if (buttonInsts.inst === this) {
          buttonInsts.splice(i, 1);
          break;
        }
      }

      return this;
    },

    /**
     * Enable / disable a button
     * @param  {node} node Button node
     * @param  {boolean} [flag=true] Enable / disable flag
     * @return {Buttons} Self for chaining
     */
    enable: function (node, flag) {
      if (flag === false) {
        return this.disable(node);
      }

      var button = this._nodeToButton(node);
      $(button.node)
        .removeClass(this.c.dom.button.disabled)
        .removeAttr('disabled');

      return this;
    },

    /**
     * Get the instance name for the button set selector
     * @return {string} Instance name
     */
    name: function () {
      return this.c.name;
    },

    /**
     * Get a button's node of the buttons container if no button is given
     * @param  {node} [node] Button node
     * @return {jQuery} Button element, or container
     */
    node: function (node) {
      if (!node) {
        return this.dom.container;
      }

      var button = this._nodeToButton(node);
      return $(button.node);
    },

    /**
     * Set / get a processing class on the selected button
     * @param {element} node Triggering button node
     * @param  {boolean} flag true to add, false to remove, undefined to get
     * @return {boolean|Buttons} Getter value or this if a setter.
     */
    processing: function (node, flag) {
      var dt = this.s.dt;
      var button = this._nodeToButton(node);

      if (flag === undefined) {
        return $(button.node).hasClass('processing');
      }

      $(button.node).toggleClass('processing', flag);

      $(dt.table().node()).triggerHandler('buttons-processing.dt', [
        flag, dt.button(node), dt, $(node), button.conf
      ]);

      return this;
    },

    /**
     * Remove a button.
     * @param  {node} node Button node
     * @return {Buttons} Self for chaining
     */
    remove: function (node) {
      var button = this._nodeToButton(node);
      var host = this._nodeToHost(node);
      var dt = this.s.dt;

      // Remove any child buttons first
      if (button.buttons.length) {
        for (var i = button.buttons.length - 1; i >= 0; i--) {
          this.remove(button.buttons[i].node);
        }
      }

      // Allow the button to remove event handlers, etc
      if (button.conf.destroy) {
        button.conf.destroy.call(dt.button(node), dt, $(node), button.conf);
      }

      this._removeKey(button.conf);

      $(button.node).remove();

      var idx = $.inArray(button, host);
      host.splice(idx, 1);

      return this;
    },

    /**
     * Get the text for a button
     * @param  {int|string} node Button index
     * @return {string} Button text
     *//**
     * Set the text for a button
     * @param  {int|string|function} node Button index
     * @param  {string} label Text
     * @return {Buttons} Self for chaining
     */
    text: function (node, label) {
      var button = this._nodeToButton(node);
      var buttonLiner = this.c.dom.collection.buttonLiner;
      var linerTag = button.inCollection && buttonLiner && buttonLiner.tag ?
        buttonLiner.tag :
        this.c.dom.buttonLiner.tag;
      var dt = this.s.dt;
      var jqNode = $(button.node);
      var text = function (opt) {
        return typeof opt === 'function' ?
          opt(dt, jqNode, button.conf) :
          opt;
      };

      if (label === undefined) {
        return text(button.conf.text);
      }

      button.conf.text = label;

      if (linerTag) {
        jqNode.children(linerTag).html(text(label));
      } else {
        jqNode.html(text(label));
      }

      return this;
    },


    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Constructor
	 */

    /**
     * Buttons constructor
     * @private
     */
    _constructor: function () {
      var that = this;
      var dt = this.s.dt;
      var dtSettings = dt.settings()[0];
      var buttons = this.c.buttons;

      if (!dtSettings._buttons) {
        dtSettings._buttons = [];
      }

      dtSettings._buttons.push({
        inst: this,
        name: this.c.name
      });

      for (var i = 0, ien = buttons.length; i < ien; i++) {
        this.add(buttons[i]);
      }

      dt.on('destroy', function (e, settings) {
        if (settings === dtSettings) {
          that.destroy();
        }
      });

      // Global key event binding to listen for button keys
      $('body').on('keyup.' + this.s.namespace, function (e) {
        if (!document.activeElement || document.activeElement === document.body) {
          // SUse a string of characters for fast lookup of if we need to
          // handle this
          var character = String.fromCharCode(e.keyCode).toLowerCase();

          if (that.s.listenKeys.toLowerCase().indexOf(character) !== -1) {
            that._keypress(character, e);
          }
        }
      });
    },


    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Private methods
	 */

    /**
     * Add a new button to the key press listener
     * @param {object} conf Resolved button configuration object
     * @private
     */
    _addKey: function (conf) {
      if (conf.key) {
        this.s.listenKeys += $.isPlainObject(conf.key) ?
          conf.key.key :
          conf.key;
      }
    },

    /**
     * Insert the buttons into the container. Call without parameters!
     * @param  {node} [container] Recursive only - Insert point
     * @param  {array} [buttons] Recursive only - Buttons array
     * @private
     */
    _draw: function (container, buttons) {
      if (!container) {
        container = this.dom.container;
        buttons = this.s.buttons;
      }

      container.children().detach();

      for (var i = 0, ien = buttons.length; i < ien; i++) {
        container.append(buttons[i].inserter);
        container.append(' ');

        if (buttons[i].buttons && buttons[i].buttons.length) {
          this._draw(buttons[i].collection, buttons[i].buttons);
        }
      }
    },

    /**
     * Create buttons from an array of buttons
     * @param  {array} attachTo Buttons array to attach to
     * @param  {object} button Button definition
     * @param  {boolean} inCollection true if the button is in a collection
     * @private
     */
    _expandButton: function (attachTo, button, inCollection, attachPoint) {
      var dt = this.s.dt;
      var buttonCounter = 0;
      var buttons = !Array.isArray(button) ?
        [button] :
        button;

      for (var i = 0, ien = buttons.length; i < ien; i++) {
        var conf = this._resolveExtends(buttons[i]);

        if (!conf) {
          continue;
        }

        // If the configuration is an array, then expand the buttons at this
        // point
        if (Array.isArray(conf)) {
          this._expandButton(attachTo, conf, inCollection, attachPoint);
          continue;
        }

        var built = this._buildButton(conf, inCollection);
        if (!built) {
          continue;
        }

        if (attachPoint !== undefined && attachPoint !== null) {
          attachTo.splice(attachPoint, 0, built);
          attachPoint++;
        } else {
          attachTo.push(built);
        }

        if (built.conf.buttons) {
          built.collection = $('<' + this.c.dom.collection.tag + '/>');

          built.conf._collection = built.collection;

          this._expandButton(built.buttons, built.conf.buttons, true, attachPoint);
        }

        // init call is made here, rather than buildButton as it needs to
        // be selectable, and for that it needs to be in the buttons array
        if (conf.init) {
          conf.init.call(dt.button(built.node), dt, $(built.node), conf);
        }

        buttonCounter++;
      }
    },

    /**
     * Create an individual button
     * @param  {object} config            Resolved button configuration
     * @param  {boolean} inCollection `true` if a collection button
     * @return {jQuery} Created button node (jQuery)
     * @private
     */
    _buildButton: function (config, inCollection) {
      var buttonDom = this.c.dom.button;
      var linerDom = this.c.dom.buttonLiner;
      var collectionDom = this.c.dom.collection;
      var dt = this.s.dt;
      var text = function (opt) {
        return typeof opt === 'function' ?
          opt(dt, button, config) :
          opt;
      };

      if (inCollection && collectionDom.button) {
        buttonDom = collectionDom.button;
      }

      if (inCollection && collectionDom.buttonLiner) {
        linerDom = collectionDom.buttonLiner;
      }

      // Make sure that the button is available based on whatever requirements
      // it has. For example, Flash buttons require Flash
      if (config.available && !config.available(dt, config)) {
        return false;
      }

      var action = function (e, dt, button, config) {
        config.action.call(dt.button(button), e, dt, button, config);

        $(dt.table().node()).triggerHandler('buttons-action.dt', [
          dt.button(button), dt, button, config
        ]);
      };

      var tag = config.tag || buttonDom.tag;
      var clickBlurs = config.clickBlurs === undefined ? true : config.clickBlurs
      var button = $('<' + tag + '/>')
        .addClass(buttonDom.className)
        .attr('tabindex', this.s.dt.settings()[0].iTabIndex)
        .attr('aria-controls', this.s.dt.table().node().id)
        .on('click.dtb', function (e) {
          e.preventDefault();

          if (!button.hasClass(buttonDom.disabled) && config.action) {
            action(e, dt, button, config);
          }
          if (clickBlurs) {
            button.trigger('blur');
          }
        })
        .on('keyup.dtb', function (e) {
          if (e.keyCode === 13) {
            if (!button.hasClass(buttonDom.disabled) && config.action) {
              action(e, dt, button, config);
            }
          }
        });

      // Make `a` tags act like a link
      if (tag.toLowerCase() === 'a') {
        button.attr('href', '#');
      }

      // Button tags should have `type=button` so they don't have any default behaviour
      if (tag.toLowerCase() === 'button') {
        button.attr('type', 'button');
      }

      if (linerDom.tag) {
        var liner = $('<' + linerDom.tag + '/>')
          .html(text(config.text))
          .addClass(linerDom.className);

        if (linerDom.tag.toLowerCase() === 'a') {
          liner.attr('href', '#');
        }

        button.append(liner);
      } else {
        button.html(text(config.text));
      }

      if (config.enabled === false) {
        button.addClass(buttonDom.disabled);
      }

      if (config.className) {
        button.addClass(config.className);
      }

      if (config.titleAttr) {
        button.attr('title', text(config.titleAttr));
      }

      if (config.attr) {
        button.attr(config.attr);
      }

      if (!config.namespace) {
        config.namespace = '.dt-button-' + (_buttonCounter++);
      }

      var buttonContainer = this.c.dom.buttonContainer;
      var inserter;
      if (buttonContainer && buttonContainer.tag) {
        inserter = $('<' + buttonContainer.tag + '/>')
          .addClass(buttonContainer.className)
          .append(button);
      } else {
        inserter = button;
      }

      this._addKey(config);

      // Style integration callback for DOM manipulation
      // Note that this is _not_ documented. It is currently
      // for style integration only
      if (this.c.buttonCreated) {
        inserter = this.c.buttonCreated(config, inserter);
      }

      return {
        conf: config,
        node: button.get(0),
        inserter: inserter,
        buttons: [],
        inCollection: inCollection,
        collection: null
      };
    },

    /**
     * Get the button object from a node (recursive)
     * @param  {node} node Button node
     * @param  {array} [buttons] Button array, uses base if not defined
     * @return {object} Button object
     * @private
     */
    _nodeToButton: function (node, buttons) {
      if (!buttons) {
        buttons = this.s.buttons;
      }

      for (var i = 0, ien = buttons.length; i < ien; i++) {
        if (buttons[i].node === node) {
          return buttons[i];
        }

        if (buttons[i].buttons.length) {
          var ret = this._nodeToButton(node, buttons[i].buttons);

          if (ret) {
            return ret;
          }
        }
      }
    },

    /**
     * Get container array for a button from a button node (recursive)
     * @param  {node} node Button node
     * @param  {array} [buttons] Button array, uses base if not defined
     * @return {array} Button's host array
     * @private
     */
    _nodeToHost: function (node, buttons) {
      if (!buttons) {
        buttons = this.s.buttons;
      }

      for (var i = 0, ien = buttons.length; i < ien; i++) {
        if (buttons[i].node === node) {
          return buttons;
        }

        if (buttons[i].buttons.length) {
          var ret = this._nodeToHost(node, buttons[i].buttons);

          if (ret) {
            return ret;
          }
        }
      }
    },

    /**
     * Handle a key press - determine if any button's key configured matches
     * what was typed and trigger the action if so.
     * @param  {string} character The character pressed
     * @param  {object} e Key event that triggered this call
     * @private
     */
    _keypress: function (character, e) {
      // Check if this button press already activated on another instance of Buttons
      if (e._buttonsHandled) {
        return;
      }

      var run = function (conf, node) {
        if (!conf.key) {
          return;
        }

        if (conf.key === character) {
          e._buttonsHandled = true;
          $(node).click();
        } else if ($.isPlainObject(conf.key)) {
          if (conf.key.key !== character) {
            return;
          }

          if (conf.key.shiftKey && !e.shiftKey) {
            return;
          }

          if (conf.key.altKey && !e.altKey) {
            return;
          }

          if (conf.key.ctrlKey && !e.ctrlKey) {
            return;
          }

          if (conf.key.metaKey && !e.metaKey) {
            return;
          }

          // Made it this far - it is good
          e._buttonsHandled = true;
          $(node).click();
        }
      };

      var recurse = function (a) {
        for (var i = 0, ien = a.length; i < ien; i++) {
          run(a[i].conf, a[i].node);

          if (a[i].buttons.length) {
            recurse(a[i].buttons);
          }
        }
      };

      recurse(this.s.buttons);
    },

    /**
     * Remove a key from the key listener for this instance (to be used when a
     * button is removed)
     * @param  {object} conf Button configuration
     * @private
     */
    _removeKey: function (conf) {
      if (conf.key) {
        var character = $.isPlainObject(conf.key) ?
          conf.key.key :
          conf.key;

        // Remove only one character, as multiple buttons could have the
        // same listening key
        var a = this.s.listenKeys.split('');
        var idx = $.inArray(character, a);
        a.splice(idx, 1);
        this.s.listenKeys = a.join('');
      }
    },

    /**
     * Resolve a button configuration
     * @param  {string|function|object} conf Button config to resolve
     * @return {object} Button configuration
     * @private
     */
    _resolveExtends: function (conf) {
      var dt = this.s.dt;
      var i, ien;
      var toConfObject = function (base) {
        var loop = 0;

        // Loop until we have resolved to a button configuration, or an
        // array of button configurations (which will be iterated
        // separately)
        while (!$.isPlainObject(base) && !Array.isArray(base)) {
          if (base === undefined) {
            return;
          }

          if (typeof base === 'function') {
            base = base(dt, conf);

            if (!base) {
              return false;
            }
          } else if (typeof base === 'string') {
            if (!_dtButtons[base]) {
              throw 'Unknown button type: ' + base;
            }

            base = _dtButtons[base];
          }

          loop++;
          if (loop > 30) {
            // Protect against misconfiguration killing the browser
            throw 'Buttons: Too many iterations';
          }
        }

        return Array.isArray(base) ?
          base :
          $.extend({}, base);
      };

      conf = toConfObject(conf);

      while (conf && conf.extend) {
        // Use `toConfObject` in case the button definition being extended
        // is itself a string or a function
        if (!_dtButtons[conf.extend]) {
          throw 'Cannot extend unknown button type: ' + conf.extend;
        }

        var objArray = toConfObject(_dtButtons[conf.extend]);
        if (Array.isArray(objArray)) {
          return objArray;
        } else if (!objArray) {
          // This is a little brutal as it might be possible to have a
          // valid button without the extend, but if there is no extend
          // then the host button would be acting in an undefined state
          return false;
        }

        // Stash the current class name
        var originalClassName = objArray.className;

        conf = $.extend({}, objArray, conf);

        // The extend will have overwritten the original class name if the
        // `conf` object also assigned a class, but we want to concatenate
        // them so they are list that is combined from all extended buttons
        if (originalClassName && conf.className !== originalClassName) {
          conf.className = originalClassName + ' ' + conf.className;
        }

        // Buttons to be added to a collection  -gives the ability to define
        // if buttons should be added to the start or end of a collection
        var postfixButtons = conf.postfixButtons;
        if (postfixButtons) {
          if (!conf.buttons) {
            conf.buttons = [];
          }

          for (i = 0, ien = postfixButtons.length; i < ien; i++) {
            conf.buttons.push(postfixButtons[i]);
          }

          conf.postfixButtons = null;
        }

        var prefixButtons = conf.prefixButtons;
        if (prefixButtons) {
          if (!conf.buttons) {
            conf.buttons = [];
          }

          for (i = 0, ien = prefixButtons.length; i < ien; i++) {
            conf.buttons.splice(i, 0, prefixButtons[i]);
          }

          conf.prefixButtons = null;
        }

        // Although we want the `conf` object to overwrite almost all of
        // the properties of the object being extended, the `extend`
        // property should come from the object being extended
        conf.extend = objArray.extend;
      }

      return conf;
    },

    /**
     * Display (and replace if there is an existing one) a popover attached to a button
     * @param {string|node} content Content to show
     * @param {DataTable.Api} hostButton DT API instance of the button
     * @param {object} inOpts Options (see object below for all options)
     */
    _popover: function (content, hostButton, inOpts) {
      var dt = hostButton;
      var buttonsSettings = this.c;
      var options = $.extend({
        align: 'button-left', // button-right, dt-container
        autoClose: false,
        background: true,
        backgroundClassName: 'dt-button-background',
        contentClassName: buttonsSettings.dom.collection.className,
        collectionLayout: '',
        collectionTitle: '',
        dropup: false,
        fade: 400,
        rightAlignClassName: 'dt-button-right',
        tag: buttonsSettings.dom.collection.tag
      }, inOpts);
      var hostNode = hostButton.node();

      var close = function () {
        _fadeOut(
          $('.dt-button-collection'),
          options.fade,
          function () {
            $(this).detach();
          }
        );

        $(dt.buttons('[aria-haspopup="true"][aria-expanded="true"]').nodes())
          .attr('aria-expanded', 'false');

        $('div.dt-button-background').off('click.dtb-collection');
        Buttons.background(false, options.backgroundClassName, options.fade, hostNode);

        $('body').off('.dtb-collection');
        dt.off('buttons-action.b-internal');
      };

      if (content === false) {
        close();
      }

      var existingExpanded = $(dt.buttons('[aria-haspopup="true"][aria-expanded="true"]').nodes());
      if (existingExpanded.length) {
        hostNode = existingExpanded.eq(0);

        close();
      }

      var display = $('<div/>')
        .addClass('dt-button-collection')
        .addClass(options.collectionLayout)
        .css('display', 'none');

      content = $(content)
        .addClass(options.contentClassName)
        .attr('role', 'menu')
        .appendTo(display);

      hostNode.attr('aria-expanded', 'true');

      if (hostNode.parents('body')[0] !== document.body) {
        hostNode = document.body.lastChild;
      }

      if (options.collectionTitle) {
        display.prepend('<div class="dt-button-collection-title">' + options.collectionTitle + '</div>');
      }

      _fadeIn(display.insertAfter(hostNode), options.fade);

      var tableContainer = $(hostButton.table().container());
      var position = display.css('position');

      if (options.align === 'dt-container') {
        hostNode = hostNode.parent();
        display.css('width', tableContainer.width());
      }

      // Align the popover relative to the DataTables container
      // Useful for wide popovers such as SearchPanes
      if (
        position === 'absolute' &&
        (
          display.hasClass(options.rightAlignClassName) ||
          display.hasClass(options.leftAlignClassName) ||
          options.align === 'dt-container'
        )
      ) {

        var hostPosition = hostNode.position();

        display.css({
          top: hostPosition.top + hostNode.outerHeight(),
          left: hostPosition.left
        });

        // calculate overflow when positioned beneath
        var collectionHeight = display.outerHeight();
        var tableBottom = tableContainer.offset().top + tableContainer.height();
        var listBottom = hostPosition.top + hostNode.outerHeight() + collectionHeight;
        var bottomOverflow = listBottom - tableBottom;

        // calculate overflow when positioned above
        var listTop = hostPosition.top - collectionHeight;
        var tableTop = tableContainer.offset().top;
        var topOverflow = tableTop - listTop;

        // if bottom overflow is larger, move to the top because it fits better, or if dropup is requested
        var moveTop = hostPosition.top - collectionHeight - 5;
        if ((bottomOverflow > topOverflow || options.dropup) && -moveTop < tableTop) {
          display.css('top', moveTop);
        }

        // Get the size of the container (left and width - and thus also right)
        var tableLeft = tableContainer.offset().left;
        var tableWidth = tableContainer.width();
        var tableRight = tableLeft + tableWidth;

        // Get the size of the popover (left and width - and ...)
        var popoverLeft = display.offset().left;
        var popoverWidth = display.width();
        var popoverRight = popoverLeft + popoverWidth;

        // Get the size of the host buttons (left and width - and ...)
        var buttonsLeft = hostNode.offset().left;
        var buttonsWidth = hostNode.outerWidth()
        var buttonsRight = buttonsLeft + buttonsWidth;

        // You've then got all the numbers you need to do some calculations and if statements,
        //  so we can do some quick JS maths and apply it only once
        // If it has the right align class OR the buttons are right aligned OR the button container is floated right,
        //  then calculate left position for the popover to align the popover to the right hand
        //  side of the button - check to see if the left of the popover is inside the table container.
        // If not, move the popover so it is, but not more than it means that the popover is to the right of the table container
        var popoverShuffle = 0;
        if (display.hasClass(options.rightAlignClassName)) {
          popoverShuffle = buttonsRight - popoverRight;
          if (tableLeft > (popoverLeft + popoverShuffle)) {
            var leftGap = tableLeft - (popoverLeft + popoverShuffle);
            var rightGap = tableRight - (popoverRight + popoverShuffle);

            if (leftGap > rightGap) {
              popoverShuffle += rightGap;
            } else {
              popoverShuffle += leftGap;
            }
          }
        }
          // else attempt to left align the popover to the button. Similar to above, if the popover's right goes past the table container's right,
        //  then move it back, but not so much that it goes past the left of the table container
        else {
          popoverShuffle = tableLeft - popoverLeft;

          if (tableRight < (popoverRight + popoverShuffle)) {
            var leftGap = tableLeft - (popoverLeft + popoverShuffle);
            var rightGap = tableRight - (popoverRight + popoverShuffle);

            if (leftGap > rightGap) {
              popoverShuffle += rightGap;
            } else {
              popoverShuffle += leftGap;
            }

          }
        }

        display.css('left', display.position().left + popoverShuffle);

      } else if (position === 'absolute') {
        // Align relative to the host button
        var hostPosition = hostNode.position();

        display.css({
          top: hostPosition.top + hostNode.outerHeight(),
          left: hostPosition.left
        });

        // calculate overflow when positioned beneath
        var collectionHeight = display.outerHeight();
        var top = hostNode.offset().top
        var popoverShuffle = 0;

        // Get the size of the host buttons (left and width - and ...)
        var buttonsLeft = hostNode.offset().left;
        var buttonsWidth = hostNode.outerWidth()
        var buttonsRight = buttonsLeft + buttonsWidth;

        // Get the size of the popover (left and width - and ...)
        var popoverLeft = display.offset().left;
        var popoverWidth = content.width();
        var popoverRight = popoverLeft + popoverWidth;

        var moveTop = hostPosition.top - collectionHeight - 5;
        var tableBottom = tableContainer.offset().top + tableContainer.height();
        var listBottom = hostPosition.top + hostNode.outerHeight() + collectionHeight;
        var bottomOverflow = listBottom - tableBottom;

        // calculate overflow when positioned above
        var listTop = hostPosition.top - collectionHeight;
        var tableTop = tableContainer.offset().top;
        var topOverflow = tableTop - listTop;

        if ((bottomOverflow > topOverflow || options.dropup) && -moveTop < tableTop) {
          display.css('top', moveTop);
        }

        popoverShuffle = options.align === 'button-right'
          ? buttonsRight - popoverRight
          : buttonsLeft - popoverLeft;

        display.css('left', display.position().left + popoverShuffle);
      } else {
        // Fix position - centre on screen
        var top = display.height() / 2;
        if (top > $(window).height() / 2) {
          top = $(window).height() / 2;
        }

        display.css('marginTop', top * -1);
      }

      if (options.background) {
        Buttons.background(true, options.backgroundClassName, options.fade, hostNode);
      }

      // This is bonkers, but if we don't have a click listener on the
      // background element, iOS Safari will ignore the body click
      // listener below. An empty function here is all that is
      // required to make it work...
      $('div.dt-button-background').on('click.dtb-collection', function () {
      });

      $('body')
        .on('click.dtb-collection', function (e) {
          // andSelf is deprecated in jQ1.8, but we want 1.7 compat
          var back = $.fn.addBack ? 'addBack' : 'andSelf';
          var parent = $(e.target).parent()[0];

          if ((!$(e.target).parents()[back]().filter(content).length && !$(parent).hasClass('dt-buttons')) || $(e.target).hasClass('dt-button-background')) {
            close();
          }
        })
        .on('keyup.dtb-collection', function (e) {
          if (e.keyCode === 27) {
            close();
          }
        });

      if (options.autoClose) {
        setTimeout(function () {
          dt.on('buttons-action.b-internal', function (e, btn, dt, node) {
            if (node[0] === hostNode[0]) {
              return;
            }
            close();
          });
        }, 0);
      }

      $(display).trigger('buttons-popover.dt');
    }
  });


  /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Statics
 */

  /**
   * Show / hide a background layer behind a collection
   * @param  {boolean} Flag to indicate if the background should be shown or
   *   hidden
   * @param  {string} Class to assign to the background
   * @static
   */
  Buttons.background = function (show, className, fade, insertPoint) {
    if (fade === undefined) {
      fade = 400;
    }
    if (!insertPoint) {
      insertPoint = document.body;
    }

    if (show) {
      _fadeIn(
        $('<div/>')
          .addClass(className)
          .css('display', 'none')
          .insertAfter(insertPoint),
        fade
      );
    } else {
      _fadeOut(
        $('div.' + className),
        fade,
        function () {
          $(this)
            .removeClass(className)
            .remove();
        }
      );
    }
  };

  /**
   * Instance selector - select Buttons instances based on an instance selector
   * value from the buttons assigned to a DataTable. This is only useful if
   * multiple instances are attached to a DataTable.
   * @param  {string|int|array} Instance selector - see `instance-selector`
   *   documentation on the DataTables site
   * @param  {array} Button instance array that was attached to the DataTables
   *   settings object
   * @return {array} Buttons instances
   * @static
   */
  Buttons.instanceSelector = function (group, buttons) {
    if (group === undefined || group === null) {
      return $.map(buttons, function (v) {
        return v.inst;
      });
    }

    var ret = [];
    var names = $.map(buttons, function (v) {
      return v.name;
    });

    // Flatten the group selector into an array of single options
    var process = function (input) {
      if (Array.isArray(input)) {
        for (var i = 0, ien = input.length; i < ien; i++) {
          process(input[i]);
        }
        return;
      }

      if (typeof input === 'string') {
        if (input.indexOf(',') !== -1) {
          // String selector, list of names
          process(input.split(','));
        } else {
          // String selector individual name
          var idx = $.inArray(input.trim(), names);

          if (idx !== -1) {
            ret.push(buttons[idx].inst);
          }
        }
      } else if (typeof input === 'number') {
        // Index selector
        ret.push(buttons[input].inst);
      }
    };

    process(group);

    return ret;
  };

  /**
   * Button selector - select one or more buttons from a selector input so some
   * operation can be performed on them.
   * @param  {array} Button instances array that the selector should operate on
   * @param  {string|int|node|jQuery|array} Button selector - see
   *   `button-selector` documentation on the DataTables site
   * @return {array} Array of objects containing `inst` and `idx` properties of
   *   the selected buttons so you know which instance each button belongs to.
   * @static
   */
  Buttons.buttonSelector = function (insts, selector) {
    var ret = [];
    var nodeBuilder = function (a, buttons, baseIdx) {
      var button;
      var idx;

      for (var i = 0, ien = buttons.length; i < ien; i++) {
        button = buttons[i];

        if (button) {
          idx = baseIdx !== undefined ?
            baseIdx + i :
            i + '';

          a.push({
            node: button.node,
            name: button.conf.name,
            idx: idx
          });

          if (button.buttons) {
            nodeBuilder(a, button.buttons, idx + '-');
          }
        }
      }
    };

    var run = function (selector, inst) {
      var i, ien;
      var buttons = [];
      nodeBuilder(buttons, inst.s.buttons);

      var nodes = $.map(buttons, function (v) {
        return v.node;
      });

      if (Array.isArray(selector) || selector instanceof $) {
        for (i = 0, ien = selector.length; i < ien; i++) {
          run(selector[i], inst);
        }
        return;
      }

      if (selector === null || selector === undefined || selector === '*') {
        // Select all
        for (i = 0, ien = buttons.length; i < ien; i++) {
          ret.push({
            inst: inst,
            node: buttons[i].node
          });
        }
      } else if (typeof selector === 'number') {
        // Main button index selector
        ret.push({
          inst: inst,
          node: inst.s.buttons[selector].node
        });
      } else if (typeof selector === 'string') {
        if (selector.indexOf(',') !== -1) {
          // Split
          var a = selector.split(',');

          for (i = 0, ien = a.length; i < ien; i++) {
            run(a[i].trim(), inst);
          }
        } else if (selector.match(/^\d+(\-\d+)*$/)) {
          // Sub-button index selector
          var indexes = $.map(buttons, function (v) {
            return v.idx;
          });

          ret.push({
            inst: inst,
            node: buttons[$.inArray(selector, indexes)].node
          });
        } else if (selector.indexOf(':name') !== -1) {
          // Button name selector
          var name = selector.replace(':name', '');

          for (i = 0, ien = buttons.length; i < ien; i++) {
            if (buttons[i].name === name) {
              ret.push({
                inst: inst,
                node: buttons[i].node
              });
            }
          }
        } else {
          // jQuery selector on the nodes
          $(nodes).filter(selector).each(function () {
            ret.push({
              inst: inst,
              node: this
            });
          });
        }
      } else if (typeof selector === 'object' && selector.nodeName) {
        // Node selector
        var idx = $.inArray(selector, nodes);

        if (idx !== -1) {
          ret.push({
            inst: inst,
            node: nodes[idx]
          });
        }
      }
    };


    for (var i = 0, ien = insts.length; i < ien; i++) {
      var inst = insts[i];

      run(selector, inst);
    }

    return ret;
  };


  /**
   * Buttons defaults. For full documentation, please refer to the docs/option
   * directory or the DataTables site.
   * @type {Object}
   * @static
   */
  Buttons.defaults = {
    buttons: ['copy', 'excel', 'csv', 'pdf', 'print'],
    name: 'main',
    tabIndex: 0,
    dom: {
      container: {
        tag: 'div',
        className: 'dt-buttons'
      },
      collection: {
        tag: 'div',
        className: ''
      },
      button: {
        // Flash buttons will not work with `<button>` in IE - it has to be `<a>`
        tag: 'ActiveXObject' in window ?
          'a' :
          'button',
        className: 'dt-button',
        active: 'active',
        disabled: 'disabled'
      },
      buttonLiner: {
        tag: 'span',
        className: ''
      }
    }
  };

  /**
   * Version information
   * @type {string}
   * @static
   */
  Buttons.version = '1.6.5';


  $.extend(_dtButtons, {
    collection: {
      text: function (dt) {
        return dt.i18n('buttons.collection', 'Collection');
      },
      className: 'buttons-collection',
      init: function (dt, button, config) {
        button.attr('aria-expanded', false);
      },
      action: function (e, dt, button, config) {
        e.stopPropagation();

        if (config._collection.parents('body').length) {
          this.popover(false, config);
        } else {
          this.popover(config._collection, config);
        }
      },
      attr: {
        'aria-haspopup': true
      }
      // Also the popover options, defined in Buttons.popover
    },
    copy: function (dt, conf) {
      if (_dtButtons.copyHtml5) {
        return 'copyHtml5';
      }
      if (_dtButtons.copyFlash && _dtButtons.copyFlash.available(dt, conf)) {
        return 'copyFlash';
      }
    },
    csv: function (dt, conf) {
      // Common option that will use the HTML5 or Flash export buttons
      if (_dtButtons.csvHtml5 && _dtButtons.csvHtml5.available(dt, conf)) {
        return 'csvHtml5';
      }
      if (_dtButtons.csvFlash && _dtButtons.csvFlash.available(dt, conf)) {
        return 'csvFlash';
      }
    },
    excel: function (dt, conf) {
      // Common option that will use the HTML5 or Flash export buttons
      if (_dtButtons.excelHtml5 && _dtButtons.excelHtml5.available(dt, conf)) {
        return 'excelHtml5';
      }
      if (_dtButtons.excelFlash && _dtButtons.excelFlash.available(dt, conf)) {
        return 'excelFlash';
      }
    },
    pdf: function (dt, conf) {
      // Common option that will use the HTML5 or Flash export buttons
      if (_dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available(dt, conf)) {
        return 'pdfHtml5';
      }
      if (_dtButtons.pdfFlash && _dtButtons.pdfFlash.available(dt, conf)) {
        return 'pdfFlash';
      }
    },
    pageLength: function (dt) {
      var lengthMenu = dt.settings()[0].aLengthMenu;
      var vals = Array.isArray(lengthMenu[0]) ? lengthMenu[0] : lengthMenu;
      var lang = Array.isArray(lengthMenu[0]) ? lengthMenu[1] : lengthMenu;
      var text = function (dt) {
        return dt.i18n('buttons.pageLength', {
          "-1": 'Show all rows',
          _: 'Show %d rows'
        }, dt.page.len());
      };

      return {
        extend: 'collection',
        text: text,
        className: 'buttons-page-length',
        autoClose: true,
        buttons: $.map(vals, function (val, i) {
          return {
            text: lang[i],
            className: 'button-page-length',
            action: function (e, dt) {
              dt.page.len(val).draw();
            },
            init: function (dt, node, conf) {
              var that = this;
              var fn = function () {
                that.active(dt.page.len() === val);
              };

              dt.on('length.dt' + conf.namespace, fn);
              fn();
            },
            destroy: function (dt, node, conf) {
              dt.off('length.dt' + conf.namespace);
            }
          };
        }),
        init: function (dt, node, conf) {
          var that = this;
          dt.on('length.dt' + conf.namespace, function () {
            that.text(conf.text);
          });
        },
        destroy: function (dt, node, conf) {
          dt.off('length.dt' + conf.namespace);
        }
      };
    }
  });


  /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * DataTables API
 *
 * For complete documentation, please refer to the docs/api directory or the
 * DataTables site
 */

// Buttons group and individual button selector
  DataTable.Api.register('buttons()', function (group, selector) {
    // Argument shifting
    if (selector === undefined) {
      selector = group;
      group = undefined;
    }

    this.selector.buttonGroup = group;

    var res = this.iterator(true, 'table', function (ctx) {
      if (ctx._buttons) {
        return Buttons.buttonSelector(
          Buttons.instanceSelector(group, ctx._buttons),
          selector
        );
      }
    }, true);

    res._groupSelector = group;
    return res;
  });

// Individual button selector
  DataTable.Api.register('button()', function (group, selector) {
    // just run buttons() and truncate
    var buttons = this.buttons(group, selector);

    if (buttons.length > 1) {
      buttons.splice(1, buttons.length);
    }

    return buttons;
  });

// Active buttons
  DataTable.Api.registerPlural('buttons().active()', 'button().active()', function (flag) {
    if (flag === undefined) {
      return this.map(function (set) {
        return set.inst.active(set.node);
      });
    }

    return this.each(function (set) {
      set.inst.active(set.node, flag);
    });
  });

// Get / set button action
  DataTable.Api.registerPlural('buttons().action()', 'button().action()', function (action) {
    if (action === undefined) {
      return this.map(function (set) {
        return set.inst.action(set.node);
      });
    }

    return this.each(function (set) {
      set.inst.action(set.node, action);
    });
  });

// Enable / disable buttons
  DataTable.Api.register(['buttons().enable()', 'button().enable()'], function (flag) {
    return this.each(function (set) {
      set.inst.enable(set.node, flag);
    });
  });

// Disable buttons
  DataTable.Api.register(['buttons().disable()', 'button().disable()'], function () {
    return this.each(function (set) {
      set.inst.disable(set.node);
    });
  });

// Get button nodes
  DataTable.Api.registerPlural('buttons().nodes()', 'button().node()', function () {
    var jq = $();

    // jQuery will automatically reduce duplicates to a single entry
    $(this.each(function (set) {
      jq = jq.add(set.inst.node(set.node));
    }));

    return jq;
  });

// Get / set button processing state
  DataTable.Api.registerPlural('buttons().processing()', 'button().processing()', function (flag) {
    if (flag === undefined) {
      return this.map(function (set) {
        return set.inst.processing(set.node);
      });
    }

    return this.each(function (set) {
      set.inst.processing(set.node, flag);
    });
  });

// Get / set button text (i.e. the button labels)
  DataTable.Api.registerPlural('buttons().text()', 'button().text()', function (label) {
    if (label === undefined) {
      return this.map(function (set) {
        return set.inst.text(set.node);
      });
    }

    return this.each(function (set) {
      set.inst.text(set.node, label);
    });
  });

// Trigger a button's action
  DataTable.Api.registerPlural('buttons().trigger()', 'button().trigger()', function () {
    return this.each(function (set) {
      set.inst.node(set.node).trigger('click');
    });
  });

// Button resolver to the popover
  DataTable.Api.register('button().popover()', function (content, options) {
    return this.map(function (set) {
      return set.inst._popover(content, this.button(this[0].node), options);
    });
  });

// Get the container elements
  DataTable.Api.register('buttons().containers()', function () {
    var jq = $();
    var groupSelector = this._groupSelector;

    // We need to use the group selector directly, since if there are no buttons
    // the result set will be empty
    this.iterator(true, 'table', function (ctx) {
      if (ctx._buttons) {
        var insts = Buttons.instanceSelector(groupSelector, ctx._buttons);

        for (var i = 0, ien = insts.length; i < ien; i++) {
          jq = jq.add(insts[i].container());
        }
      }
    });

    return jq;
  });

  DataTable.Api.register('buttons().container()', function () {
    // API level of nesting is `buttons()` so we can zip into the containers method
    return this.containers().eq(0);
  });

// Add a new button
  DataTable.Api.register('button().add()', function (idx, conf) {
    var ctx = this.context;

    // Don't use `this` as it could be empty - select the instances directly
    if (ctx.length) {
      var inst = Buttons.instanceSelector(this._groupSelector, ctx[0]._buttons);

      if (inst.length) {
        inst[0].add(conf, idx);
      }
    }

    return this.button(this._groupSelector, idx);
  });

// Destroy the button sets selected
  DataTable.Api.register('buttons().destroy()', function () {
    this.pluck('inst').unique().each(function (inst) {
      inst.destroy();
    });

    return this;
  });

// Remove a button
  DataTable.Api.registerPlural('buttons().remove()', 'buttons().remove()', function () {
    this.each(function (set) {
      set.inst.remove(set.node);
    });

    return this;
  });

// Information box that can be used by buttons
  var _infoTimer;
  DataTable.Api.register('buttons.info()', function (title, message, time) {
    var that = this;

    if (title === false) {
      this.off('destroy.btn-info');
      _fadeOut(
        $('#datatables_buttons_info'),
        400,
        function () {
          $(this).remove();
        }
      );
      clearTimeout(_infoTimer);
      _infoTimer = null;

      return this;
    }

    if (_infoTimer) {
      clearTimeout(_infoTimer);
    }

    if ($('#datatables_buttons_info').length) {
      $('#datatables_buttons_info').remove();
    }

    title = title ? '<h2>' + title + '</h2>' : '';

    _fadeIn(
      $('<div id="datatables_buttons_info" class="dt-button-info"/>')
        .html(title)
        .append($('<div/>')[typeof message === 'string' ? 'html' : 'append'](message))
        .css('display', 'none')
        .appendTo('body')
    );

    if (time !== undefined && time !== 0) {
      _infoTimer = setTimeout(function () {
        that.buttons.info(false);
      }, time);
    }

    this.on('destroy.btn-info', function () {
      that.buttons.info(false);
    });

    return this;
  });

// Get data from the table for export - this is common to a number of plug-in
// buttons so it is included in the Buttons core library
  DataTable.Api.register('buttons.exportData()', function (options) {
    if (this.context.length) {
      return _exportData(new DataTable.Api(this.context[0]), options);
    }
  });

// Get information about the export that is common to many of the export data
// types (DRY)
  DataTable.Api.register('buttons.exportInfo()', function (conf) {
    if (!conf) {
      conf = {};
    }

    return {
      filename: _filename(conf),
      title: _title(conf),
      messageTop: _message(this, conf.message || conf.messageTop, 'top'),
      messageBottom: _message(this, conf.messageBottom, 'bottom')
    };
  });


  /**
   * Get the file name for an exported file.
   *
   * @param {object}  config Button configuration
   * @param {boolean} incExtension Include the file name extension
   */
  var _filename = function (config) {
    // Backwards compatibility
    var filename = config.filename === '*' && config.title !== '*' && config.title !== undefined && config.title !== null && config.title !== '' ?
      config.title :
      config.filename;

    if (typeof filename === 'function') {
      filename = filename();
    }

    if (filename === undefined || filename === null) {
      return null;
    }

    if (filename.indexOf('*') !== -1) {
      filename = filename.replace('*', $('head > title').text()).trim();
    }

    // Strip characters which the OS will object to
    filename = filename.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, "");

    var extension = _stringOrFunction(config.extension);
    if (!extension) {
      extension = '';
    }

    return filename + extension;
  };

  /**
   * Simply utility method to allow parameters to be given as a function
   *
   * @param {undefined|string|function} option Option
   * @return {null|string} Resolved value
   */
  var _stringOrFunction = function (option) {
    if (option === null || option === undefined) {
      return null;
    } else if (typeof option === 'function') {
      return option();
    }
    return option;
  };

  /**
   * Get the title for an exported file.
   *
   * @param {object} config  Button configuration
   */
  var _title = function (config) {
    var title = _stringOrFunction(config.title);

    return title === null ?
      null : title.indexOf('*') !== -1 ?
        title.replace('*', $('head > title').text() || 'Exported data') :
        title;
  };

  var _message = function (dt, option, position) {
    var message = _stringOrFunction(option);
    if (message === null) {
      return null;
    }

    var caption = $('caption', dt.table().container()).eq(0);
    if (message === '*') {
      var side = caption.css('caption-side');
      if (side !== position) {
        return null;
      }

      return caption.length ?
        caption.text() :
        '';
    }

    return message;
  };


  var _exportTextarea = $('<textarea/>')[0];
  var _exportData = function (dt, inOpts) {
    var config = $.extend(true, {}, {
      rows: null,
      columns: '',
      modifier: {
        search: 'applied',
        order: 'applied'
      },
      orthogonal: 'display',
      stripHtml: true,
      stripNewlines: true,
      decodeEntities: true,
      trim: true,
      format: {
        header: function (d) {
          return strip(d);
        },
        footer: function (d) {
          return strip(d);
        },
        body: function (d) {
          return strip(d);
        }
      },
      customizeData: null
    }, inOpts);

    var strip = function (str) {
      if (typeof str !== 'string') {
        return str;
      }

      // Always remove script tags
      str = str.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');

      // Always remove comments
      str = str.replace(/<!\-\-.*?\-\->/g, '');

      if (config.stripHtml) {
        str = str.replace(/<([^>'"]*('[^']*'|"[^"]*")?)*>/g, '');
      }

      if (config.trim) {
        str = str.replace(/^\s+|\s+$/g, '');
      }

      if (config.stripNewlines) {
        str = str.replace(/\n/g, ' ');
      }

      if (config.decodeEntities) {
        _exportTextarea.innerHTML = str;
        str = _exportTextarea.value;
      }

      return str;
    };


    var header = dt.columns(config.columns).indexes().map(function (idx) {
      var el = dt.column(idx).header();
      return config.format.header(el.innerHTML, idx, el);
    }).toArray();

    var footer = dt.table().footer() ?
      dt.columns(config.columns).indexes().map(function (idx) {
        var el = dt.column(idx).footer();
        return config.format.footer(el ? el.innerHTML : '', idx, el);
      }).toArray() :
      null;

    // If Select is available on this table, and any rows are selected, limit the export
    // to the selected rows. If no rows are selected, all rows will be exported. Specify
    // a `selected` modifier to control directly.
    var modifier = $.extend({}, config.modifier);
    if (dt.select && typeof dt.select.info === 'function' && modifier.selected === undefined) {
      if (dt.rows(config.rows, $.extend({selected: true}, modifier)).any()) {
        $.extend(modifier, {selected: true})
      }
    }

    var rowIndexes = dt.rows(config.rows, modifier).indexes().toArray();
    var selectedCells = dt.cells(rowIndexes, config.columns);
    var cells = selectedCells
      .render(config.orthogonal)
      .toArray();
    var cellNodes = selectedCells
      .nodes()
      .toArray();

    var columns = header.length;
    var rows = columns > 0 ? cells.length / columns : 0;
    var body = [];
    var cellCounter = 0;

    for (var i = 0, ien = rows; i < ien; i++) {
      var row = [columns];

      for (var j = 0; j < columns; j++) {
        row[j] = config.format.body(cells[cellCounter], i, j, cellNodes[cellCounter]);
        cellCounter++;
      }

      body[i] = row;
    }

    var data = {
      header: header,
      footer: footer,
      body: body
    };

    if (config.customizeData) {
      config.customizeData(data);
    }

    return data;
  };


  /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * DataTables interface
 */

// Attach to DataTables objects for global access
  $.fn.dataTable.Buttons = Buttons;
  $.fn.DataTable.Buttons = Buttons;


// DataTables creation - check if the buttons have been defined for this table,
// they will have been if the `B` option was used in `dom`, otherwise we should
// create the buttons instance here so they can be inserted into the document
// using the API. Listen for `init` for compatibility with pre 1.10.10, but to
// be removed in future.
  $(document).on('init.dt plugin-init.dt', function (e, settings) {
    if (e.namespace !== 'dt') {
      return;
    }

    var opts = settings.oInit.buttons || DataTable.defaults.buttons;

    if (opts && !settings._buttons) {
      new Buttons(settings, opts).container();
    }
  });

  function _init(settings, options) {
    var api = new DataTable.Api(settings);
    var opts = options
      ? options
      : api.init().buttons || DataTable.defaults.buttons;

    return new Buttons(api, opts).container();
  }

// DataTables `dom` feature option
  DataTable.ext.feature.push({
    fnInit: _init,
    cFeature: "B"
  });

// DataTables 2 layout feature
  if (DataTable.ext.features) {
    DataTable.ext.features.register('buttons', _init);
  }


  return Buttons;
}));
